﻿///////////////////////////////////////////////////////////////////////////////
//
//  This source is part of the  "Managed Task Framework.NET Runtime"  (MTF.NET Runtime).
//
//
//  Copyright (c) 2011, Multicore Consulting Limited (UK)
//  All rights reserved.
//  http://www.multicoreconsulting.co.uk
//
//  Redistribution and use in source and binary forms, with or without
//  modification, are permitted provided that the following conditions
//  are met:
//
//  -  Redistributions of source code must retain the above copyright
//     notice, this list of conditions and the following disclaimer.
//  -  Redistributions in binary form must reproduce the above copyright
//     notice, this list of conditions and the following disclaimer in
//     the documentation and/or other materials provided with the
//     distribution.
//  -  Neither the name of Multicore Consulting Limited nor the names of
//     its contributors may be used to endorse or promote products
//     derived from this software without specific prior written
//     permission.
//
//  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
//  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
//  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
//  FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
//  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
//  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
//  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
//  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
//  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
//  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
//  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
//  POSSIBILITY OF SUCH DAMAGE.
//
///////////////////////////////////////////////////////////////////////////////
//#define MODIFIER_TRACE

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using MTFOrg.MTF.MTFRuntime;
using MTFOrg.MTF.MTFCore.Messages;
using System.Diagnostics;

namespace MTFOrg.MTF.MTFCore
{
    /// <summary>
    /// Class implementing the Modifier object.
    /// </summary>
    partial class Modifier
    {
#if MODIFIER_TRACE
        const string TRACE_OBJ = "RepBottom";
#endif
        /// <summary>
        /// Defines an interface for handling an action. 
        /// </summary>
        public interface IModifierHandler
        {
            /// <summary>
            /// Handles an action.
            /// </summary>
            /// <param name="actionEvent">Event that triggered the action</param>
            /// <param name="elementNumber">Actioner element number</param>
            void OnEvent(EventDictionary eventToModify, int elementNumber);
        }

        /// <summary>
        /// Default actioner handler called when none is provided.
        /// </summary>
        public class DefaultModifierHandler : IModifierHandler
        {
            /// <summary>
            /// Modifier owning this default modifier handler.
            /// </summary>
            private Modifier m_Modifier;

            /// <summary>
            /// Default modification handler.
            /// </summary>
            /// <param name="eventToModify">Event to modify</param>
            /// <param name="elementNumber">Modifier element number</param>
            void IModifierHandler.OnEvent(EventDictionary eventToModify, int elementNumber)
            {
                Console.WriteLine("Modifier '{0}' triggered on element number {1}.", m_Modifier.Name, elementNumber);
            }

            /// <summary>
            /// Constructor.
            /// </summary>
            /// <param name="actioner">Modifier owning this default modifier handler</param>
            public DefaultModifierHandler(Modifier modifier)
            {
                m_Modifier = modifier;
            }
        }

        protected class ModifierState
        {
            protected Queue<int> m_QueuedQueries;

            public Queue<int> QueuedQueries
            {
                get { return m_QueuedQueries; }
                set { m_QueuedQueries = value; }
            }

            public ModifierState()
            {
                m_QueuedQueries = new Queue<int>();
            }
        }

        protected class ModifierOverlayState
        {
            Event m_InputEvent;

            public Event InputEvent
            {
                get { return m_InputEvent; }
                set { m_InputEvent = value; }
            }

            public ModifierOverlayState()
            {
                m_InputEvent = null;
            }
        }

        /// <summary>
        /// Handler class instance.
        /// </summary>
        protected IModifierHandler m_ModifierHandler;

        /// <summary>
        /// Invoked to initialize this task.
        /// </summary>
        public override void OnInitialize()
        {
            base.OnInitialize();

            // Create the handler class to handle the action
            object handlerObject = GetProperty("HandlerClass");
            Type type = handlerObject as Type;
            if (type != null)
            {
                m_ModifierHandler = (IModifierHandler)Activator.CreateInstance(type);
            }
            else
            {
                m_ModifierHandler = new DefaultModifierHandler(this);
            }

            // Hook up to the producer-consumer event notifications
            m_ProducerConsumerTask.QueryReceived += new ProducerConsumer.OnQuery(OnQueryReceived);
            m_ProducerConsumerTask.ReleaseReceived += new ProducerConsumer.OnRelease(OnReleaseReceived);
            m_ProducerConsumerTask.EventReceived += new ProducerConsumer.OnEvent(OnEventReceived);
            m_ProducerConsumerTask.ReleasedReceived += new ProducerConsumer.OnReleased(OnReleasedReceived);
        }

        /// <summary>
        /// Handles received query.
        /// </summary>
        /// <param name="producerLinkNum">Link that query was received on</param>
        /// <param name="consumerSlots">Consumer slot states (not used)</param>
        /// <param name="producerSlots">Slot states</param>
        /// <param name="elementState">State associated with the current element</param>
        /// <param name="elementNumber">Number of the current element</param>
        public void OnQueryReceived(int producerLinkNum, Slots slots, int consumerSlot, int producerSlot, ref object elementState, int elementNumber)
        {
#if MODIFIER_TRACE
			string statii = string.Empty;
			if (producerSlot != Slots.NO_SLOT)
			{
				for (int _i = 0; _i < m_ProducerConsumerTask.NumProducerLinks; _i++)
					statii += " " + (
                        slots.GetProducerLinkStatus(producerSlot, _i) == LinkStatus.Inactive ? "I" :
						slots.GetProducerLinkStatus(producerSlot, _i) == LinkStatus.Opened ? "O" :
						slots.GetProducerLinkStatus(producerSlot, _i) == LinkStatus.Waiting ? "W" : "R");
			}
            if (TRACE_OBJ == string.Empty || TRACE_OBJ == Name) Debug.Print(Name + "[{1}]: Query modifier link {0}" + statii, producerLinkNum, elementNumber);
#endif
            ModifierState ModifierState = elementState as ModifierState;
            if (ModifierState == null)
            {
                elementState = ModifierState = new ModifierState();
            }

            ModifierOverlayState overlayState = slots.GetProducerTag(producerSlot) as ModifierOverlayState;
            if (overlayState == null)
            {
                overlayState = new ModifierOverlayState();
                slots.SetProducerTag(producerSlot, overlayState);
            }

            // Attempt to send a query on the only consumer link
            if (!m_ProducerConsumerTask.SendQuery(0, producerSlot, elementNumber))
            {
                // If it could not be sent at this time then queue it
                ModifierState.QueuedQueries.Enqueue(producerSlot);
            }
#if MODIFIER_TRACE
            else
            {
                if (TRACE_OBJ == string.Empty || TRACE_OBJ == Name) Debug.Print(Name + "[{0}]: Query modifier link", elementNumber);
            }
#endif
        }

        /// <summary>
        /// Handles received release.
        /// </summary>
        /// <param name="producerLinkNum">Link that query was received on</param>
        /// <param name="consumerSlots">Consumer slot states (not used)</param>
        /// <param name="producerSlots">Slot states</param>
        /// <param name="eventToRelease">The event to be released</param>
        /// <param name="isRollback">Whether the release was a roll-back</param>
        /// <param name="elementState">State associated with the current element</param>
        /// <param name="elementNumber">Number of the current element</param>
        /// <returns>Whether to complete the current slot set</returns>
        public bool OnReleaseReceived(int producerLinkNum, Slots slots, int consumerSlot, int producerSlot, Event eventToRelease, bool isRollback, bool deletedSlot, ref object elementState, int elementNumber)
        {
#if MODIFIER_TRACE
			string statii = string.Empty;
			if (producerSlot != Slots.NO_SLOT)
			{
				for (int _i = 0; _i < m_ProducerConsumerTask.NumProducerLinks; _i++)
					statii += " " + (
                        slots.GetProducerLinkStatus(producerSlot, _i) == LinkStatus.Inactive ? "I" :
						slots.GetProducerLinkStatus(producerSlot, _i) == LinkStatus.Opened ? "O" :
						slots.GetProducerLinkStatus(producerSlot, _i) == LinkStatus.Waiting ? "W" : "R");
			}
            if (TRACE_OBJ == string.Empty || TRACE_OBJ == Name) Debug.Print(Name + "[{1}]: Release modifier link {0}" + statii, producerLinkNum, elementNumber);
#endif

#if MODIFIER_TRACE
            if (TRACE_OBJ == string.Empty || TRACE_OBJ == Name) Debug.Print(Name + "[{0}]: Release modifier link", elementNumber);
#endif
            ModifierOverlayState overlayState = slots.GetProducerTag(producerSlot) as ModifierOverlayState;
            m_ProducerConsumerTask.SendRelease(consumerSlot, overlayState.InputEvent, isRollback, 0, elementNumber, true);

            return true;
        }

        /// <summary>
        /// Called when the producer notifies it has finished with an event.
        /// </summary>
        /// <param name="consumerLinkNum">Link that query was received on</param>
        /// <param name="consumerSlots">Consumer slot states (not used)</param>
        /// <param name="producerSlots">Slot states</param>
        /// <param name="elementState">State associated with the current element</param>
        /// <param name="elementNumber">Number of the current element</param>
        public void OnReleasedReceived(int consumerLinkNum, ref object elementState, int elementNumber)
        {
            // Handle any queued queries
            ModifierState modifierState = elementState as ModifierState;
            if (modifierState.QueuedQueries.Count > 0)
            {
                int queuedProducerSlot = modifierState.QueuedQueries.Peek();

                // Attempt to send a query on the only consumer link
                if (m_ProducerConsumerTask.SendQuery(0, queuedProducerSlot, elementNumber))
                {
                    modifierState.QueuedQueries.Dequeue();
#if MODIFIER_TRACE
                    if (TRACE_OBJ == string.Empty || TRACE_OBJ == Name) Debug.Print(Name + "[{0}]: Query modifier link", elementNumber);
#endif
                }
                else
                {
#if DEBUG
                    // If a query could not be sent following a release then an error has occurred
                    throw new Exception("Unable to send queued query when slot should have been available.");
#endif
                }
            }
        }

        /// <summary>
        /// Called when the ProducerConsumer receives an event
        /// </summary>
        /// <param name="consumerLinkNum">Consumer link (absolute) on which the event arrived</param>
        /// <param name="consumerSlots">Consumer slot state information</param>
        /// <param name="producerSlots">Producer slot state information</param>
        /// <param name="eventReceived">Event that was received</param>
        /// <param name="elementState">State associated with the current element</param>
        /// <param name="elementNumber">Number of the current element</param>
        public void OnEventReceived(int consumerLinkNum, Slots slots, int consumerSlot, int producerSlot, Event eventReceived, ref object elementState, int elementNumber)
        {
#if MODIFIER_TRACE
			string statii = string.Empty;
			if (producerSlot != Slots.NO_SLOT)
			{
				for (int _i = 0; _i < m_ProducerConsumerTask.NumProducerLinks; _i++)
					statii += " " + (
                        slots.GetProducerLinkStatus(producerSlot, _i) == LinkStatus.Inactive ? "I" :
						slots.GetProducerLinkStatus(producerSlot, _i) == LinkStatus.Opened ? "O" :
						slots.GetProducerLinkStatus(producerSlot, _i) == LinkStatus.Waiting ? "W" : "R");
			}
            if (TRACE_OBJ == string.Empty || TRACE_OBJ == Name) Debug.Print(Name + "[{0}]: Modifier event" + statii, elementNumber);
#endif
            // Record the received event
            ModifierOverlayState overlayState = slots.GetProducerTag(producerSlot) as ModifierOverlayState;
            overlayState.InputEvent = eventReceived;

            // Modify the event
            m_ModifierHandler.OnEvent(eventReceived as EventDictionary, elementNumber);

            // Send the received event to the link that is waiting
            for (int i = 0; i < m_ProducerConsumerTask.NumProducerLinks; i++)
            {
                if (slots.GetProducerLinkStatus(producerSlot, i) == LinkStatus.Waiting)
                {
                    m_ProducerConsumerTask.SendEvent(i, producerSlot, eventReceived, elementNumber);
#if SENDEVENT_TRACE
                    Debug.Print("{0}[{1}] link {2}", Name, elementNumber, i);
#endif
                }
            }

            // Deal with queued query
            ModifierState modifierState = elementState as ModifierState;
            if (modifierState.QueuedQueries.Count > 0)
            {
                if (m_ProducerConsumerTask.SendQuery(0, modifierState.QueuedQueries.Peek(), elementNumber))
                {
                    modifierState.QueuedQueries.Dequeue();
                }
            }
        }
    }
}
